package com.gua.encrypt.advice;

import java.lang.reflect.Type;

import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import com.gua.common.response.AjaxResult;
import com.gua.encrypt.annotation.Encryption;
import com.gua.encrypt.constants.CryptoConstant;
import com.gua.encrypt.dto.EncryptDataBaseDTO;
import com.gua.encrypt.utils.AESUtil;

import cn.hutool.crypto.CryptoException;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

/**
 * @author huangjj
 * @description: responseBody自动解密
 */
@Slf4j
@ConditionalOnProperty(prefix = "crypto", name = "type", havingValue = CryptoConstant.TYPE_AES, matchIfMissing = false)
@ControllerAdvice
public class AESEncryptResponseBodyAdvice implements ResponseBodyAdvice<AjaxResult<?>> {

	public AESEncryptResponseBodyAdvice() {
		log.info("AESEncryptResponseBodyAdvice init!!");
	}

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        ParameterizedTypeImpl genericParameterType = (ParameterizedTypeImpl)returnType.getGenericParameterType();
        // 如果直接是Result并且有解密注解，则处理
        if (genericParameterType.getRawType() == AjaxResult.class 
        		&& returnType.hasMethodAnnotation(Encryption.class)) {
            return true;
        }
        // 如果不是ResponseBody或者是AjaxResult，则放行
        if (genericParameterType.getRawType() != ResponseEntity.class) {
            return false;
        }
        // 如果是ResponseEntity<AjaxResult>并且有解密注解，则处理
        for (Type type : genericParameterType.getActualTypeArguments()) {
            if (((ParameterizedTypeImpl) type).getRawType() == AjaxResult.class 
            		&& returnType.hasMethodAnnotation(Encryption.class)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public AjaxResult<?> beforeBodyWrite(AjaxResult<?> body, 
    		MethodParameter returnType, 
    		MediaType selectedContentType, 
    		Class<? extends HttpMessageConverter<?>> selectedConverterType, 
    		ServerHttpRequest request, 
    		ServerHttpResponse response) {
        // 真实数据
        Object data = body.getData();
        // 如果data为空，直接返回
        if (data == null) {
            return body;
        }
        // 如果是实体，并且继承了EncryptDataBaseDTO，则放入时间戳
        if (data instanceof EncryptDataBaseDTO) {
        	EncryptDataBaseDTO encryptDataBaseDTO = (EncryptDataBaseDTO)data;
        	encryptDataBaseDTO.setCurrentTimeMillis(System.currentTimeMillis());
        	encryptDataBaseDTO.setRandom(null);
        	encryptDataBaseDTO.setSign(null);
        }
        String dataText = JSONUtil.toJsonStr(data);
        // 如果data为空，直接返回
        if (StringUtils.isBlank(dataText)) {
            return body;
        }
        // 如果位数小于16，报错
        if (dataText.length() < 16) {
            throw new CryptoException("加密失败，数据小于16位");
        }
        String encryptText = AESUtil.encryptHex(dataText);
        AjaxResult<String> result = new AjaxResult<>();
        result.setCode(body.getCode());
        result.setMsg(body.getMsg());
        result.setData(encryptText);
        return result;
    }
}
